import os
import re
import tempfile
import random

import requests
import json
import socket
import sys
from typing import List
from urllib.parse import urlparse

# os.system('pip3 install -r ./requirements.txt')
import wget
import yaml
import exrex
import paramiko
import paramiko.util
import dns.resolver
import dns.exception
import tldextract
from alibabacloud_alidns20150109.client import Client as Alidns20150109Client
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_alidns20150109 import models as alidns_20150109_models
from alibabacloud_tea_util import models as util_models
from alibabacloud_tea_util.client import Client as UtilClient


class Node:
    def __init__(self, link_type=None, name=None, domain=None, endpoint='domain', ip=None, port=22, user='root',
                 password=None, ssh_key=None, force_parsing=False, master=False, etcd=False, worker=False,
                 aliyun_config=None, loadbalancer=None, known_hosts_tmp=None):
        self.ali_config = aliyun_config
        self.link_type = link_type
        self.name = name
        self.domain = domain
        self.domain_parse_ip = None
        self.default_network_card = None
        self.endpoint = endpoint
        self.ip = ip
        self.local_ip = None
        self.port = port
        self.user = user
        self.password = password
        self.ssh_key = ssh_key
        self.force_parsing = force_parsing
        self.master = master
        self.etcd = etcd
        self.worker = worker
        self.loadbalancer = loadbalancer
        self.connected = False
        self.country = None
        self.known_hosts_tmp = known_hosts_tmp
        if self.link_type is None:
            self.link_type = 'ssh'
        if self.domain is not None:
            try:
                self.domain_parse_ip = dns.resolver.Resolver().resolve(self.domain, 'A')[0].to_text()
            except dns.resolver.NXDOMAIN as e:
                self.domain_parse_ip = None
        if self.link_type == 'ssh':
            if self.ip is None:
                self.ip = self.domain_parse_ip
            if self.ip is None:
                self.print_error('无法获得节点IP地址')
            self.ssh = paramiko.SSHClient()
            # windows下,不存在创建文件
            if not os.path.exists(self.known_hosts_tmp):
                open(self.known_hosts_tmp, 'w').close()
            # self.ssh.load_host_keys(self.known_hosts_tmp)
            self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            try:
                if self.password:
                    self.ssh.connect(hostname=self.ip, port=self.port, username=self.user, password=self.password,
                                     timeout=1)
                elif self.ssh_key:
                    self.ssh.connect(hostname=self.ip, port=self.port, username=self.user, key_filename=self.ssh_key,
                                     timeout=1)
                else:
                    self.ssh.connect(hostname=self.ip, port=self.port, username=self.user, timeout=1)
            except Exception as e:
                self.print_error('ssh连接失败')
            self.sftp = self.ssh.open_sftp()
            read = self.send_cmd('curl 100.100.100.200 --connect-timeout 0.01')
            if "Connection timed out" in read:
                self.in_aliyun = False
            else:
                self.in_aliyun = True
        else:
            read = self.send_cmd('curl 100.100.100.200 --connect-timeout 0.01')
            if "Connection timed out" in read:
                self.in_aliyun = False
            else:
                self.in_aliyun = True
            if self.ip is None:
                if self.in_aliyun:
                    self.ip = self.send_cmd('curl http://100.100.100.200/latest/meta-data/eipv4 --connect-timeout 0.01')
                else:
                    r = self.send_cmd('curl http://ip-api.com/json/?fields=query,country')
                    self.ip = json.loads(r)['query']
                    self.country = json.loads(r)['country']
        r = self.send_py('localip.py')
        self.local_ip = r.strip()
        if self.ip is not None \
                and self.domain_parse_ip != self.ip:
            if self.force_parsing and self.ali_config is not None:
                try:
                    self.aliyun_parse_domain()
                except Exception as e:
                    self.print_error('自动解析失败:' + e.__str__())
            else:
                self.print_error(f'请手动解析域名{self.domain}到{self.ip}')
        if self.name is None:
            self.name = self.send_cmd('hostname').lower()
        if self.country is None:
            r = self.send_cmd('curl http://ip-api.com/json/?fields=query,country')
            self.country = json.loads(r)['country']
        if self.default_network_card is None:
            self.default_network_card = self.send_cmd('ip route | grep default | awk \'{print $5}\'')

    def send_cmd(self, cmd):
        if self.link_type == 'ssh':
            stdin, stdout, stderr = self.ssh.exec_command(cmd)
            return stdout.read().decode('utf-8').strip()
        else:
            return os.popen(cmd).read().strip()

    def send_py(self, file):
        if self.link_type == 'ssh':
            r = self.send_cmd('mktemp')
            self.sftp.put(f"{file}", f'{r}')
            stdin, stdout, stderr = self.ssh.exec_command(f'python3 {r}')
            # self.send_cmd(f"unlink {r}")
            return stdout.read().decode('utf-8').strip()
        else:
            return os.popen(f'python3 {file}').read().strip()

    def print_error(self, error):
        print(self.name)
        print(error)
        exit(1)

    def aliyun_parse_domain(self):

        self.ali_config.endpoint = 'alidns.aliyuncs.com'
        client = Alidns20150109Client(self.ali_config)
        # 通过子域名获取主域名
        domain_info = tldextract.extract(self.domain)
        domain = domain_info.domain + '.' + domain_info.suffix
        rr = domain_info.subdomain
        runtime = util_models.RuntimeOptions()
        delete_sub_domain_records_request = alidns_20150109_models.DeleteSubDomainRecordsRequest(
            domain_name=domain,
            rr=rr
        )
        client.delete_sub_domain_records_with_options(delete_sub_domain_records_request, runtime)
        # 获取主域名的解析记录
        add_domain_record_request = alidns_20150109_models.AddDomainRecordRequest(
            domain_name=domain,
            type='A',
            value=self.ip,
            rr=rr
        )
        client.add_domain_record_with_options(add_domain_record_request, runtime)

    def check_install_from_apt(self, package_name, version):
        is_new_install = False
        print(f'检查 {package_name}')
        current_info = self.send_cmd(f'apt-cache policy {package_name}')
        current_version = re.findall(r'Installed: (.*)', current_info)[0]
        if current_version == '(none)':
            print(f'安装{package_name}')
            self.send_cmd(f'apt-get install -y {package_name}={version}')
            is_new_install = True
        # 检查 {package_name} 版本
        print(f'检查 {package_name} 版本')
        current_info = self.send_cmd(f'apt-cache policy {package_name}')
        current_version = re.findall(r'Installed: (.*)', current_info)[0]
        if current_version != version:
            print(f'{package_name} 版本不匹配 {current_version} != {version}')
            exit(1)
        return is_new_install

    def download_file(self, down_url):
        if 'github' in down_url and self.country in ['CN', 'cn', 'China', 'china']:
            down_url = 'https://ghproxy.com/' + down_url
        file_name = self.send_cmd('mktemp')
        self.send_cmd('wget -O ' + file_name + ' ' + down_url)
        return file_name

    def get_cpu_instruction_set(self):
        return self.send_cmd('dpkg --print-architecture')


def trust_node(nodes, known_hosts_tmp):
    # 通过接口获取密钥
    response = requests.get('https://api.github.com/users/linshuboy/keys')
    # 写入密钥到authorized_keys文件
    sync_known_hosts_tmp_path = known_hosts_tmp
    authorized_keys_path = tempfile.mktemp()
    authorized_keys_path_needle = open(authorized_keys_path, "w")
    for key in response.json():
        authorized_keys_path_needle.write(key['key'] + '\n')
    # 循环链接所有的IP地址
    for node in nodes:
        tmp_path = tempfile.mktemp()
        r = node['object'].send_cmd('pwd')
        node['object'].sftp.get(r + '/.ssh/id_rsa.pub', tmp_path)
        for line in open(tmp_path, "r"):
            authorized_keys_path_needle.write(line.split()[0] + ' ' + line.split()[1] + '\n')
        os.remove(tmp_path)
    authorized_keys_path_needle.close()
    for node in nodes:
        r = node['object'].send_cmd('pwd')
        node['object'].sftp.put(authorized_keys_path, r + '/.ssh/authorized_keys')
        node['object'].sftp.put(sync_known_hosts_tmp_path, r + '/.ssh/known_hosts')
    os.remove(authorized_keys_path)
    os.remove(sync_known_hosts_tmp_path)


if __name__ == '__main__':
    known_hosts_tmp = tempfile.mktemp()
    #   通过config.json配置文件获取信息
    #   获取之前删除配置文件的注释
    with open('config.json', 'r', encoding='utf-8') as f:
        config = json.load(f)
    #   初始化配置文件中的aliyun
    ali_config = None
    if 'AccessKeyID' in config and 'AccessKeySecret' in config:
        ali_config = open_api_models.Config(
            # 您的 AccessKey ID,
            access_key_id=config['AccessKeyID'],
            # 您的 AccessKey Secret,
            access_key_secret=config['AccessKeySecret'],
        )
    #   格式化配置文件中的IP地址
    if "//" in config:
        del config['//']
    clusters = config['clusters']
    for cluster in clusters:
        if "//" in cluster:
            del cluster['//']
        cluster['ips'] = []
        cluster['local_ips'] = []
        for node in cluster['nodes']:
            if "//" in node:
                del node['//']
            node['object'] = Node(**node, aliyun_config=ali_config, known_hosts_tmp=known_hosts_tmp)
            cluster['ips'].append(node['object'].ip)
            cluster['local_ips'].append(node['object'].local_ip)
    #   判断IP个数和节点个数去重后是否匹配
    for cluster in clusters:
        if len(cluster['ips']) != len(set(cluster['ips'])):
            print('IP地址重复')
            exit(1)
        if len(cluster['local_ips']) != len(set(cluster['local_ips'])):
            print('IP地址重复')
            exit(1)
    #   建立服务器互信
    for cluster in clusters:
        trust_node(cluster['nodes'], known_hosts_tmp)
    for cluster in clusters:
        # 带下标循环
        for index, node in enumerate(cluster['nodes']):
            #   开始初始化每一个节点的配置
            print(f"开始初始化 {node['object'].name}")
            need_apt_update = False
            if node['object'].in_aliyun:
                print('检查 docker 阿里云源证书')
                if node['object'].send_cmd('ls /usr/share/keyrings/ali-docker-archive-keyring.gpg') \
                        != '/usr/share/keyrings/ali-docker-archive-keyring.gpg':
                    print('添加docker阿里云源证书')
                    node['object'].send_cmd(
                        'curl -fsSL http://mirrors.cloud.aliyuncs.com/docker-ce/linux/ubuntu/gpg |'
                        ' gpg --dearmor -o /usr/share/keyrings/ali-docker-archive-keyring.gpg')
                print('检查 docker 阿里云源')
                if node['object'].send_cmd('ls /etc/apt/sources.list.d/ali-docker.list') \
                        != '/etc/apt/sources.list.d/ali-docker.list':
                    print('添加docker阿里云源')
                    node['object'].send_cmd(
                        'echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ali-docker-archive-keyring.gpg]'
                        ' http://mirrors.cloud.aliyuncs.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable" |'
                        ' tee /etc/apt/sources.list.d/ali-docker.list')
                    need_apt_update = True
            else:
                print('检查 docker 阿里云源证书')
                if node['object'].send_cmd('ls /usr/share/keyrings/ali-docker-archive-keyring.gpg') \
                        != '/usr/share/keyrings/ali-docker-archive-keyring.gpg':
                    print('添加docker阿里云源证书')
                    node['object'].send_cmd(
                        'curl -fsSL http://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg |'
                        ' gpg --dearmor -o /usr/share/keyrings/ali-docker-archive-keyring.gpg')
                print('检查 docker 阿里云源')
                if node['object'].send_cmd('ls /etc/apt/sources.list.d/ali-docker.list') \
                        != '/etc/apt/sources.list.d/ali-docker.list':
                    print('添加docker阿里云源')
                    node['object'].send_cmd(
                        'echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ali-docker-archive-keyring.gpg]'
                        ' http://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable" |'
                        ' tee /etc/apt/sources.list.d/ali-docker.list')
                    need_apt_update = True
            if need_apt_update:
                print('更新 apt 列表')
                node['object'].send_cmd('apt-get update')
            print('安装必要的软件包')
            packages = ['apt-transport-https', 'ca-certificates', 'curl', 'gnupg', 'lsb-release', 'dialog', 'socat',
                        'conntrack', 'ebtables', 'ipset', 'python3-pip', 'ipvsadm', 'keepalived']
            for package in packages:
                print(f'检查 {package}')
                package_info = node['object'].send_cmd(f'apt-cache policy {package}')
                install_info = re.findall(r'Installed:\s+(.*)', package_info)[0]
                if install_info == '(none)':
                    print(f'安装 {package}')
                    node['object'].send_cmd(f'apt-get install {package} -y')
            # 获取虚拟内存信息
            print('检查 虚拟内存信息')
            swap_info = node['object'].send_cmd('free -m')
            swap_total = re.findall(r'Swap:\s+(\d+)', swap_info)[0]
            swap_used = re.findall(r'Swap:\s+\d+\s+(\d+)', swap_info)[0]
            swap_free = re.findall(r'Swap:\s+\d+\s+\d+\s+(\d+)', swap_info)[0]
            if not int(swap_total) == 0:
                print('关闭虚拟内存')
                node['object'].send_cmd('swapoff -a')
            # 同步时间
            print('检查 时间同步')
            # 关闭防火墙
            print('检查 防火墙')
            r = node['object'].send_cmd('ufw status')
            if 'inactive' not in r:
                print('关闭防火墙')
                node['object'].send_cmd('ufw disable')
            # 安装containerd
            new_install = node['object'].check_install_from_apt('containerd.io', config['containerd_apt_version'])
            print(new_install)
            if new_install:
                # 设置containerd
                node['object'].send_cmd('systemctl start containerd')
                node['object'].send_cmd('systemctl enable containerd')
                # 验证containerd
                node['object'].send_cmd('containerd -v')
                # 创建containerd配置文件
                # 创建目录
                if node['object'].send_cmd('ls /etc/containerd') \
                        != '/etc/containerd':
                    node['object'].send_cmd('mkdir -p /etc/containerd')
                node['object'].send_cmd('containerd config default | tee /etc/containerd/config.toml')
                config_tmp = tempfile.mktemp()
                node['object'].sftp.get('/etc/containerd/config.toml', config_tmp)
                needle = open(config_tmp, 'r+')
                infos = needle.readlines()
                needle.seek(0, 0)
                for line in infos:
                    line_new = line \
                        .replace('sandbox_image = "k8s.gcr.io/pause:',
                                 'sandbox_image = "registry.aliyuncs.com/google_containers'
                                 '/pause:') \
                        .replace('SystemdCgroup = false', 'SystemdCgroup = true')
                    needle.write(line_new)
                needle.close()
                node['object'].sftp.put(config_tmp, '/etc/containerd/config.toml')
                # 重启containerd
                node['object'].send_cmd('systemctl restart containerd')
            #   开始安装k8s
            print('开始安装K8s')
            # 获取home目录
            if node['object'].send_cmd('ls kk') \
                    != 'kk':
                # 获取kubekey版本
                print('获取 kubekey 版本')
                r = requests.get('https://api.github.com/repos/kubesphere/kubekey/releases')
                json = r.json()
                name = ''
                url = ''
                for j in json:
                    if j['tag_name'] == config['kubekey_version']:
                        asset = j['assets']
                        for i in asset:
                            # 正则匹配kubekey版本
                            if re.match(r'kubekey-.*-linux-' + node['object'].get_cpu_instruction_set() + '.tar.gz',
                                        i['name']):
                                name = i['name']
                                url = i['browser_download_url']
                if url != '':
                    print('下载kubekey')
                    kubekey_tmp = node['object'].download_file(url)
                    print('解压kubekey' + kubekey_tmp)
                    node['object'].send_cmd('tar Cxzvvf ~ ' + kubekey_tmp)
                    node['object'].send_cmd('chmod +x ~/kk')
                    node['object'].send_cmd('unlink ' + kubekey_tmp)
                else:
                    print('未找到kubekey')
                    exit(1)
            config_tmp = node['object'].send_cmd('mktemp')
            node['object'].send_cmd(f'unlink {config_tmp}')
            node['object'].send_cmd(f'~/kk create config -f {config_tmp} '
                                    f'--with-kubesphere {config["kubesphere_version"]} '
                                    f'--with-kubernetes {config["kubernetes_version"]}')
            node['remote_config_tmp'] = config_tmp
    for cluster in clusters:
        virtual_router_id = random.randint(1, 255)
        priority = 100
        # 带下标循环
        master_index = 0
        for index, node in enumerate(cluster['nodes']):
            if node['object'].loadbalancer:
                # 初始化每个loadbalancer节点的keepalived配置
                if master_index == 0:
                    priority = 100
                elif master_index == 1:
                    priority = 50
                else:
                    priority = priority - 1
                with open('./config/keepalived.conf', 'r+') as f:
                    text = f.read()
                    text = re.sub(
                        r'( *)(virtual_router_id)( *)(.*)([\r\n]+?)',
                        rf'\1\2 {virtual_router_id}\5', text)
                    text = re.sub(
                        r'( *)( interface)( *)(.*)([\r\n]+?)',
                        rf'\1\2 {node["object"].default_network_card}\5', text)
                    text = re.sub(
                        r'( *)(priority)( *)(.*)([\r\n]+?)',
                        rf'\1\2 {priority}\5', text)
                    text = re.sub(
                        r'( *)(unicast_src_ip)( *)(.*)([\r\n]+?)',
                        rf'\1\2 {node["object"].local_ip}\5', text)
                    text = re.sub(
                        r'( *)(track_interface)( *\{)([\s\S]*?)(}.*([\r\n]+?))',
                        rf'\1\2\3\6\1    {node["object"].default_network_card}\6\1\5', text)
                    text = re.sub(
                        r'( *)(virtual_ipaddress)( *\{)([\s\S]*?)(}.*([\r\n]+?))',
                        rf'\1\2\3\6\1    {cluster["vip"]}\6\1\5', text)
                    unicast_str = rf'\1\2\3\6'
                    for node1 in cluster['nodes']:
                        if node1['object'].local_ip != node['object'].local_ip and node1['object'].master:
                            unicast_str += rf'\1    {node1["object"].local_ip}\6'
                    unicast_str = unicast_str + rf'\1\5'
                    text = re.sub(
                        r'( *)(unicast_peer)( *\{)([\s\S]*?)(}.*([\r\n]+?))',
                        unicast_str, text)
                    if index == 0:
                        text = re.sub(
                            r'( *)(state)( *)(.*)([\r\n]+?)',
                            rf'\1\2 MASTER\5', text)
                    else:
                        text = re.sub(
                            r'( *)(state)( *)(.*)([\r\n]+?)',
                            rf'\1\2 BACKUP\5', text)
                tmp = tempfile.mktemp()
                with open(tmp, 'w+') as f:
                    f.write(text)
                node["object"].sftp.put(tmp, '/etc/keepalived/keepalived.conf')
                node["object"].send_cmd('systemctl restart keepalived')
                node["object"].send_cmd('systemctl enable keepalived')
                # # 初始化每个loadbalancer的haproxy配置
                # with open('./config/haproxy.cfg', 'r+') as f:
                #     text = f.read()
                #     server_str = rf'\1\2\3\4\5\6'
                #     for node1 in cluster['nodes']:
                #         if node1['object'].master:
                #             server_str += rf'\1\7server \4-{node1["object"].name} {node1["object"].local_ip}:6443 check\5'
                #     text = re.sub(
                #         r'( *)\b(backend)( *)(.*)([\r\n]+?)((?:\1( *)[a-rt-z][a-z-]* .*[\r\n]+?)*)((?:\1 *server .*[\r\n]+?)*)',
                #         server_str, text)
                #     tmp = tempfile.mktemp()
                #     with open(tmp, 'w+') as f:
                #         f.write(text)
                #     node["object"].sftp.put(tmp, '/etc/haproxy/haproxy.cfg')
                #     r = node["object"].send_cmd('systemctl restart haproxy')
                #     print(r)
                #     node["object"].send_cmd('systemctl enable haproxy')
                # 初始化每个集群的kubekey配置
                if index == 0:
                    replacement_value_num = 0
                    replacement_value_arr = {}
                    # 第一个节点
                    local_config_tmp = tempfile.mktemp()
                    node['object'].sftp.get(node["remote_config_tmp"], local_config_tmp)
                    yamls = []
                    endpoint = ''
                    if 'endpoint' in cluster and cluster['endpoint']:
                        endpoint = cluster['endpoint']
                    with open(local_config_tmp, 'r+') as f:
                        text = f.read()
                        # 正则匹配替换
                        hosts = []
                        host_sub_str = rf'\1'
                        control_plane_sub_str = rf'\1'
                        etcd_sub_str = rf'\1'
                        worker_sub_str = rf'\1'
                        for node1 in cluster['nodes']:
                            if node1['object'].domain is not None and (node1['object'].endpoint == 'domain'):
                                address = node1['object'].domain
                                if not endpoint:
                                    endpoint = address
                            else:
                                address = node1['object'].ip
                            address = address
                            if not endpoint:
                                endpoint = 'lb.kubesphere.local'
                            internalAddress = node1['object'].local_ip
                            user = node1['object'].user
                            name = node1['object'].name
                            port = node1['object'].port
                            host_sub_str += rf'\4- {{name: {name}, ' \
                                            rf'address: {address}, ' \
                                            rf'internalAddress: {internalAddress}, ' \
                                            rf'user: {user}, ' \
                                            rf'port: {port}}}\3'
                            if node1['object'].master:
                                control_plane_sub_str += rf'\5- {name}\3'
                            if node1['object'].etcd:
                                etcd_sub_str += rf'\5- {name}\3'
                            if node1['object'].worker:
                                worker_sub_str += rf'\5- {name}\3'
                        text = re.sub(
                            r'(( *)spec:( *[\n\r]+)(?:(?:\2.*?| *?)[\n\r]+?)*?(\2 +)hosts:(?: *[\n\r]+))((?:\4- .* *[\n\r]+)*)',
                            host_sub_str, text)
                        text = re.sub(
                            r'(( *)spec:( *[\n\r]+)(?:(?:\2.*?| *?)[\n\r]+?)*?(\2 +)roleGroups:(?: *[\n\r]+)(?:(?:\4.*?| *?)[\n\r]+?)*?(\4 +)etcd:(?: *[\n\r]+))((?:\5- .* *[\n\r]+)*)',
                            etcd_sub_str, text)
                        text = re.sub(
                            r'(( *)spec:( *[\n\r]+)(?:(?:\2.*?| *?)[\n\r]+?)*?(\2 +)roleGroups:(?: *[\n\r]+)(?:(?:\4.*?| *?)[\n\r]+?)*?(\4 +)control-plane:(?: *[\n\r]+))((?:\5- .* *[\n\r]+)*)',
                            control_plane_sub_str, text)
                        text = re.sub(
                            r'(( *)spec:( *[\n\r]+)(?:(?:\2.*?| *?)[\n\r]+?)*?(\2 +)roleGroups:(?: *[\n\r]+)(?:(?:\4.*?| *?)[\n\r]+?)*?(\4 +)worker:(?: *[\n\r]+))((?:\5- .* *[\n\r]+)*)',
                            worker_sub_str, text)
                        text = re.sub(
                            r'(( *)spec:( *[\n\r]+)(?:(?:\2.*?| *?)[\n\r]+?)*?(\2 +)kubernetes:(?: *[\n\r]+)(?:(?:\4.*?| *?)[\n\r]+?)*?(?:\4 +)containerManager: *)(.*)([\n\r]+)',
                            rf'\1containerd\6', text)
                        text = re.sub(
                            r'(( *)spec:( *[\n\r]+)(?:(?:\2.*?| *?)[\n\r]+?)*?(\2 +)registry:(?: *[\n\r]+)(?:(?:\4.*?| *?)[\n\r]+?)*?(?:\4 +)registryMirrors: *)(.*)([\n\r]+)',
                            rf"\1['https://vjtojkp9.mirror.aliyuncs.com']\6", text)
                        text = re.sub(
                            r'(( *)spec:( *[\n\r]+)(?:(?:\2.*?| *?)[\n\r]+?)*?(\2 +)controlPlaneEndpoint:(?: *[\n\r]+)(?:(?:\4.*?| *?)[\n\r]+?)*?(?:\4 +)domain:)(.*)([\n\r]+)',
                            rf"\1 {endpoint}\6", text)
                        text = re.sub(
                            r'(( *)spec:( *[\n\r]+)(?:(?:\2.*?| *?)[\n\r]+?)*?(\2 +)controlPlaneEndpoint:(?: *[\n\r]+)(?:(?:\4.*?| *?)[\n\r]+?)*?(?:\4 +))# (internalLoadbalancer:)(.*)([\n\r]+)',
                            rf"\1\5\6\7", text)
                        if 'vip' in cluster and cluster['vip'] is not None:
                            text = re.sub(
                                r'(( *)spec:( *[\n\r]+)(?:(?:\2.*?| *?)[\n\r]+?)*?(\2 +)controlPlaneEndpoint:(?: *[\n\r]+)(?:(?:\4.*?| *?)[\n\r]+?)*?(?:\4 +)address:)(.*)([\n\r]+)',
                                rf"\1 {cluster['vip']}\6", text)
                        f.seek(0, 0)
                        f.write(text)
                    node['object'].sftp.put(local_config_tmp, node["remote_config_tmp"])
                    node['object'].send_cmd(f'cp {node["remote_config_tmp"]} ~/current_config.yaml')
                    # r = node['object'].send_cmd(f'~/kk create cluster -f ~/current_config.yaml --yes')
                    # print(r)
